package com.zhengb.algorithmdemo.exam.linear;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * 线性回归公式计算
 *
 * @author zhengbo
 * @date 2020/2/25
 */
public class LinearExpression {

    /**
     * 训练数据，一行一个数据，每一行最后一个数据为 y
     */
    private double[][] trainData;

    /**
     * 训练数据  行数
     */
    private int row;
    /**
     * 训练数据 列数
     */
    private int column;

    private double[] theta;

    /**
     * 训练步长
     */
    private double alpha;

    /**
     * 迭代次数
     */
    private int iterationCount;

    private List<DataPoint> dataPointList;

    private final static int ITERATION_COUNT = 10000;

    private final static double ALPHA = 0.01;

    private void initData() {
        this.column = 3;
        this.row = dataPointList.size();
        trainData = new double[this.row][this.column];

        this.alpha = ALPHA;
        this.iterationCount = ITERATION_COUNT;

        theta = new double[column - 1];

        for (int i = 0; i < theta.length; i++) {
            theta[i] = 1.0;
        }
        for (int i = 0; i < dataPointList.size(); i++) {
            DataPoint dataPoint = dataPointList.get(i);
            trainData[i][0] = dataPoint.getFactorX();
            trainData[i][1] = dataPoint.getFactorY();
            trainData[i][2] = dataPoint.getResult();
        }
    }

    public LinearExpression(List<DataPoint> dataPointList) {
        this.dataPointList = dataPointList;

        this.initData();
    }

    public void trainTheta() {
        int iteration = this.iterationCount;
        while ((iteration--) > 0) {
            //对每个theta i 求 偏导数
            double[] partialDerivative = computePartialDerivative();
            //更新每个theta
            for (int i = 0; i < theta.length; i++) {
                theta[i] = NumberUtil.sub(theta[i], NumberUtil.mul(alpha, partialDerivative[i]));
            }
        }
    }

    private double[] computePartialDerivative() {
        double[] partialDerivative = new double[theta.length];
        //遍历，对每个theta求偏导数
        for (int j = 0; j < theta.length; j++) {
            //对 theta i 求 偏导
            partialDerivative[j] = computePartialDerivativeForTheta(j);
        }
        return partialDerivative;
    }

    private double computePartialDerivativeForTheta(int j) {
        double sum = 0.0;
        //遍历 每一行数据
        for (int i = 0; i < row; i++) {
            sum = NumberUtil.add(sum, hThetaXIMinusYITimesXJI(i, j));
        }

        return NumberUtil.div(sum, row);
    }

    private double hThetaXIMinusYITimesXJI(int i, int j) {
        //取一行数据，前面是feature，最后一个是y
        double[] oneRow = getRow(i);
        double result = 0.0;

        for (int k = 0; k < (oneRow.length - 1); k++) {
            result = NumberUtil.add(result, NumberUtil.mul(theta[k], oneRow[k]));
        }
        result = NumberUtil.sub(result, oneRow[oneRow.length - 1]);
        result = NumberUtil.mul(result, oneRow[j]);
        return result;
    }

    private double[] getRow(int i) {
        return trainData[i];
    }

    public double[] getCalcFactor() {
        return theta;
    }

    /**
     * 输入数据平均误差
     *
     * @return
     */
    public double getAverageErrorRange() {

        double totalErrorRange = 0;

        for (DataPoint x : this.dataPointList) {
            double calcResult = calcPredictResult(new double[]{x.getFactorX(), x.getFactorY()});
            double error = Math.abs(NumberUtil.sub(x.getResult(), calcResult));
            double percent = NumberUtil.div(error, x.getResult());
            totalErrorRange = NumberUtil.add(totalErrorRange, percent);
        }
        return NumberUtil.div(totalErrorRange, dataPointList.size());
    }

    /**
     * 计算预测结果
     *
     * @param featureArray
     * @return
     */
    public double calcPredictResult(double[] featureArray) {

        double calcResult = NumberUtil.mul(featureArray[0], theta[0])
                + NumberUtil.mul(featureArray[1], theta[1]);

        return calcResult;
    }

    public static void main(String[] args) throws IOException {
        List<DataPoint> list = Arrays.asList(
                new DataPoint(0.18, 0.89, 41000d),
                new DataPoint(1.0, 0.26, 62000d),
                new DataPoint(0.92, 0.11, 53000.0),
                new DataPoint(0.07, 0.37, 29500.0),
                new DataPoint(0.85, 0.16, 55600.0),
                new DataPoint(0.99, 0.41, 65000.0),
                new DataPoint(0.87, 0.47, 60400.0)
        );

        LinearExpression linearExpression = new LinearExpression(list);

        linearExpression.trainTheta();

        linearExpression.getCalcFactor();

        double averageErrorRange = linearExpression.getAverageErrorRange();

        System.out.println("平均计算误差:" + averageErrorRange);

        double result1 = linearExpression.calcPredictResult(new double[]{0.49, 0.18});
        System.out.println((int) result1);

        double result2 = linearExpression.calcPredictResult(new double[]{0.57, 0.83});
        System.out.println((int) result2);

        double result3 = linearExpression.calcPredictResult(new double[]{0.56, 0.64});
        System.out.println((int) result3);

        double result4 = linearExpression.calcPredictResult(new double[]{0.76, 0.18});
        System.out.println((int) result4);

    }
}
